package org.jflame.auth.authz;

import org.jflame.commons.model.Chars;
import org.jflame.commons.util.CollectionHelper;
import org.jflame.commons.util.StringHelper;

import java.beans.Transient;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.regex.Pattern;

/**
 * url权限表示类, 这里的url表示的是接口的相对地址,即接口在系统内的地址,支持通配符*,同时支持http method限定.示例:<br>
 * <pre>
 * /user/save            具体的接口地址,不限定http method
 * /user/save[put,post]  具体的接口地址,限定http method
 * /user/*               通配地址,表示/user/开头的所有地址, *只能放到最后不能放路径中间
 * </pre>
 * 
 * @author charles.zhang
 */
public class UrlPermit extends Permit implements Serializable {

    private static final long serialVersionUID = -973361023235042678L;
    private static final Pattern urlPattern = Pattern.compile("^(/[a-zA-Z0-9_\\-\\.]+\\/?)+\\*?$");
    private static final List<String> allowHttpMethods = Arrays.asList("delete", "get", "post", "put");

    private transient List<String> methods;
    private transient boolean wildcard = false;
    private transient String path;

    private String actions;

    public UrlPermit() {
    }

    public UrlPermit(String url) {
        this(url, null);
    }

    public UrlPermit(String url, String actions) {
        setName(url);
        setActions(actions);
    }

    @Override
    public boolean implies(Permit p) {
        if (!(p instanceof UrlPermit)) {
            return false;
        }

        UrlPermit that = (UrlPermit) p;
        if (wildcard) {
            if (!that.getName()
                    .startsWith(path)) {
                return false;
            }
        } else {
            if (!path.equals(that.getName())) {
                return false;
            }
        }
        if (CollectionHelper.isNotEmpty(methods)) {
            if (CollectionHelper.isEmpty(that.getMethods())) {
                return false;
            }
            if (Collections.indexOfSubList(methods, that.getMethods()) == -1) {
                return false;
            }
        }

        return true;
    }

    public boolean implies(String _url) {
        return implies(UrlPermit.create(_url));
    }

    public void setName(String _url) {
        if (!urlPattern.matcher(_url)
                .matches()) {
            throw new IllegalArgumentException("Invalid UrlPermit string: \"" + _url + "\"");
        }
        super.setName(_url);
        char last = StringHelper.endChar(_url);
        if (last == Chars.SLASH) {
            path = StringHelper.removeLast(_url);
        } else {
            wildcard = _url.endsWith("/*");
            path = _url.substring(0, _url.length() - 1);
        }
    }

    /**
     * #getName 别名
     * 
     * @return
     */
    @Transient
    public String getUrl() {
        return getName();
    }

    public String getActions() {
        return actions;
    }

    public void setActions(String actions) {
        this.actions = StringHelper.isNotEmpty(actions) ? StringHelper.deleteWhitespace(actions) : null;
        if (this.actions != null) {
            String[] tmpArr = actions.toLowerCase()
                    .split(",");
            methods = new ArrayList<>(tmpArr.length);
            for (String m : tmpArr) {
                if (!allowHttpMethods.contains(m)) {
                    throw new IllegalArgumentException("Invalid UrlPermit :\"" + getName() + " method: " + m);
                }
                methods.add(m);
            }
            Collections.sort(methods);
        }

    }

    @Transient
    public List<String> getMethods() {
        return methods;
    }

    /**
     * 用给定规则字符串构造一个UrlPermit.
     * <p>
     * 权限url字符串由路径和要限定的http method组成,格式: /path[methods...],示例:<br>
     * <pre>
     * /user
     * /user[post,get]
     * </pre>
     *
     * @param withActionUrl
     * @return
     */
    public static UrlPermit create(String withActionUrl) {
        int squareBracketStart = withActionUrl.indexOf('[');
        if (squareBracketStart == -1) {
            return new UrlPermit(withActionUrl);
        }

        if (StringHelper.endChar(withActionUrl) != ']') {
            throw new IllegalArgumentException("Invalid withActionUrl '" + withActionUrl + "'");
        }

        String url = withActionUrl.substring(0, squareBracketStart);
        String actions = withActionUrl.substring(squareBracketStart + 1, withActionUrl.length() - 1);
        return new UrlPermit(url, actions);
    }

    @Override
    public int hashCode() {
        return Objects.hash(actions, getName());
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        UrlPermit other = (UrlPermit) obj;
        return Objects.equals(actions, other.actions) && Objects.equals(getName(), other.getName());
    }

}
